package com.telenav.snowsweeper.controller;

import java.util.HashMap;
import java.util.Random;
import java.util.Vector;

import android.app.Activity;
import android.content.Context;
import android.os.Process;
import android.os.Vibrator;
import android.view.LayoutInflater;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import com.telenav.snowsweeper.R;
import com.telenav.snowsweeper.data.Edge;
import com.telenav.snowsweeper.data.EdgeMap;
import com.telenav.snowsweeper.data.EdgeMap.InitException;
import com.telenav.snowsweeper.data.PassPoint;
import com.telenav.snowsweeper.data.Point;
import com.telenav.snowsweeper.data.PointsManager;
import com.telenav.snowsweeper.net.EvaluationRequest;
import com.telenav.snowsweeper.net.HintRequest;
import com.telenav.snowsweeper.net.ICallBack;
import com.telenav.snowsweeper.net.IRequest;
import com.telenav.snowsweeper.net.MapDataRequest;
import com.telenav.snowsweeper.net.ThreadPool;
import com.telenav.snowsweeper.renderer.MapEdgeBuilder;
import com.telenav.snowsweeper.renderer.MapEdgeRenderer;
import com.telenav.snowsweeper.renderer.RouteRenderer;
import com.telenav.snowsweeper.renderer.SurfaceRenderer;
import com.telenav.snowsweeper.renderer.Util;
import com.telenav.snowsweeper.renderer.VehicleRenderer;
import com.telenav.snowsweeper.util.DataUtil;
import com.telenav.snowsweeper.util.ResourceUtil;

public class GameController implements OnClickListener, ICallBack
{
    public static final int PRE_SNOW_SCORE = 100;
    private int score = 0;
    private int remainTime = 0; 
    private boolean gameover = false;
    private int gameLevel = 0;
    private int prefectStep = 0;
    
    private Activity context;
    private GameLoader loader;
    private GameParamsSet paramsSet;
    public boolean isVehicleSelected = false;

    private EdgeMap edgeMap;
    protected SurfaceRenderer render;
    protected MapEdgeRenderer edgeRender;
    
    protected HashMap<Integer, PassPoint> ppTotal;
    protected int currentPpIndex;

    private int[] margin = new int[2];
    
    public GameController(GameLoader loader) {
    	this.loader = loader;
    }
    
    public void startGame(int level) {
    	this.gameLevel = level;
    	this.loader.loadGame(new GameDataPackage(level), this);
    }
    
    private void init(VehicleRenderer.RendererParams veh, MapEdgeRenderer.RendererParams edge, RouteRenderer.RendererParams routeParams)
    {
        paramsSet = new GameParamsSet();
        paramsSet.init(veh, edge, routeParams);
        
        margin[0] = (ResourceUtil.loadImage(R.drawable.car).getWidth() >> 1) + 4;
        margin[1] = (ResourceUtil.loadImage(R.drawable.car).getHeight() >> 1) + 4;
        
        prefectStep = 0;
//        score = 0;
    }

    public void enterGame(final Activity context, int id, EdgeMap edgeMap, HashMap<Integer, PassPoint> passPoint)
    {
		if (passPoint == null) {
			return;
		}
		if (edgeMap == null) {
			edgeMap = fakeEdgeMap();
		}
        this.ppTotal = passPoint;
        PassPoint pp0 = passPoint.get(0);
        Point firstPoint = PointsManager.getPoint((int) pp0.longitude, (int) pp0.latitude);
        randomGenerateSnowOnEdge(edgeMap, 3);
        this.edgeMap = edgeMap;
        this.context = context;

        edgeRender = new MapEdgeRenderer(new MapEdgeBuilder(edgeMap));
        randomGenerateSnowOnEdge(edgeMap, 3);
        edgeRender.getRenderParams().setScaleXY(128, 128);

        VehicleRenderer vehicleRenderer = new VehicleRenderer();
        Point carPoint = firstPoint;
        vehicleRenderer.getRendererPamars().go2Location(carPoint);
        edgeRender.getRenderParams().move2(carPoint.getX(), carPoint.getY());
        
        currentPpIndex = 1;
        PassPoint pp = passPoint.get(1);
        Point ppPoint = PointsManager.getPoint((int) pp.longitude, (int) pp.latitude);
        Vector<Point> qw = new Vector<Point>();
        qw.add(ppPoint);
        edgeRender.getRenderParams().addPassPoints(qw);
        remainTime = (int) ((pp.timestamp - pp0.timestamp) / (60 * 1000));

        RouteRenderer routeRenderer = new RouteRenderer(edgeMap, edgeRender.getRenderParams());
        routeRenderer.getParams().addPoint(firstPoint);

        init(vehicleRenderer.getRendererPamars(), edgeRender.getRenderParams(), routeRenderer.getParams());
        
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        ViewGroup vg = (ViewGroup) inflater.inflate(R.layout.activity_game, null);
        context.setContentView(vg);

        scoreView = (TextView) vg.findViewById(R.id.score_content);
        timeView = (TextView) vg.findViewById(R.id.time_content);
        updateTime();
        updateScore();

        SurfaceView sv = (SurfaceView) vg.findViewById(id);
        sv.setLongClickable(true);
        render = new SurfaceRenderer(sv);
        render.clearObjectRenderer();
        render.addObjectRenderer(edgeRender);
        render.addObjectRenderer(vehicleRenderer);
        render.addObjectRenderer(routeRenderer);
        render.resume();

        View up = vg.findViewById(R.id.up);
        up.setOnClickListener(this);
        View down = vg.findViewById(R.id.down);
        down.setOnClickListener(this);
        View left = vg.findViewById(R.id.left);
        left.setOnClickListener(this);
        View right = vg.findViewById(R.id.right);
        right.setOnClickListener(this);

        Button hint = (Button) vg.findViewById(R.id.button_restart);
        hint.setOnClickListener(new OnClickListener()
        {
			public void onClick(View v) {
				score -= 250;
				if (score < 0) {
					score = 0;
					Random r = new Random();
					Notifier.notify(ResourceUtil.STRING_NO_SCORE_TIP[r.nextInt(2)]);
				}
				PassPoint pp = ppTotal.get(currentPpIndex);
				if (null != pp) {
                    String route = paramsSet.routeParams.getRoutePointSize() > 2 ? paramsSet.routeParams.getRouteString() : null;
                    ThreadPool.getInstance().addJob(new HintRequest(GameController.this, pp.level, pp.index - 1, pp.index, route));
				}
			}
        });
        Button backButton = (Button) vg.findViewById(R.id.button_back);
        backButton.setOnClickListener(new OnClickListener()
        {
            public void onClick(View v)
            {
                LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                ViewGroup vg = (ViewGroup) inflater.inflate(R.layout.activity_main, null);
                context.setContentView(vg);
                Button quitBt = (Button) vg.findViewById(R.id.quit);
                quitBt.setOnClickListener(new OnClickListener()
                {
                    public void onClick(View v)
                    {
                        ThreadPool.getInstance().stop();
                        context.finish();
                        Process.killProcess(Process.myPid());
                    }
                });
				Button newGameBt = (Button) vg.findViewById(R.id.start_game);
				newGameBt.setOnClickListener(new OnClickListener() {
					public void onClick(View v) {
						ThreadPool.getInstance().addJob(new MapDataRequest(GameController.this.loader, 1));
					}
				});
				score = 0;
				gameover = false;
            }
        });

        new MapEdgeTouchEventHandler(context, sv, paramsSet);
    }

    private EdgeMap fakeEdgeMap()
    {
        int[][] vPosTransTimes = new int[Util.ROW][Util.COLUMN + 1];
        int[][] vNegTransTimes = new int[Util.ROW][Util.COLUMN + 1];
        int[][] hPosTransTimes = new int[Util.ROW + 1][Util.COLUMN];
        int[][] hNegTransTimes = new int[Util.ROW + 1][Util.COLUMN];
        fakeData(vPosTransTimes);
        fakeData(vNegTransTimes);
        fakeData(hPosTransTimes);
        fakeData(hNegTransTimes);

        try
        {
            EdgeMap edgeMap = new EdgeMap(vPosTransTimes, vNegTransTimes, hPosTransTimes, hNegTransTimes);
            randomGenerateSnowOnEdge(edgeMap, 3);
            return edgeMap;
        } catch (InitException e)
        {
            e.printStackTrace();
        }
        return null;
    }

    private void fakeData(int[][] data)
    {
        Random r = new Random();
        for (int i = 0; i < data.length; ++i)
        {
            for (int j = 0; j < data[i].length; ++j)
            {
                data[i][j] = r.nextInt(3);
            }
        }
    }

    private static void randomGenerateSnowOnEdge(EdgeMap edgeMap, int maxSnow)
    {
        maxSnow += 1;
        Random r = new Random();
        int[] vSize = edgeMap.getVEdgeSize();
        if (vSize != null)
        {
            Edge edge;
            for (int i = 0; i < vSize[0]; ++i)
            {
                for (int j = 0; j < vSize[1]; ++j)
                {
                    edge = edgeMap.getVertEdge(i, j);
                    if (edge != null)
                    {
                        edge.setSnowNum(r.nextInt(maxSnow));
                    }
                }
            }
        }

        int[] hSize = edgeMap.getHEdgesSize();
        if (hSize != null)
        {
            Edge edge;
            for (int i = 0; i < hSize[0]; ++i)
            {
                for (int j = 0; j < hSize[1]; ++j)
                {
                    edge = edgeMap.getHorEdge(i, j);
                    if (edge != null)
                    {
                        edge.setSnowNum(r.nextInt(maxSnow));
                    }
                }
            }
        }
    }

    public void onClick(View v)
    {
        int[] out = new int[2];
        Point last = paramsSet.vehParams.getLocation();
        Edge e = null;
        Point p = null;
        int time = 0;
		switch (v.getId()) {
			case R.id.up:
				p = paramsSet.vehParams.go2Up();
				paramsSet.routeParams.addPoint(p);
				if (p != last && p != null) {
					last.getTopEdgeIndex(out);
					e = getEdge(out, false);
					time = e == null ? 0 : e.getNegTransTime();
				}
				break;
			case R.id.down:
				p = paramsSet.vehParams.go2Down();
				paramsSet.routeParams.addPoint(p);
				if (p != last && p != null) {
					last.getBottomEdgeIndex(out);
					e = getEdge(out, false);
					time = e == null ? 0 : e.getPosTransTime();
				}
				break;
			case R.id.left:
				p = paramsSet.vehParams.go2Left();
				paramsSet.routeParams.addPoint(p);
				if (p != last && p != null) {
					last.getLeftEdgeIndex(out);
					e = getEdge(out, true);
					time = e == null ? 0 : e.getNegTransTime();
				}
				break;
			case R.id.right:
				p = paramsSet.vehParams.go2Right();
				paramsSet.routeParams.addPoint(p);
				if (p != last && p != null) {
					last.getRightEdgeIndex(out);
					e = getEdge(out, true);
					time = e == null ? 0 : e.getPosTransTime();
				}
				break;
		}
		clearSnow(e);
		updateGameState(p, time);
    }

	public boolean parseData(IRequest request, String response) {
		if (request instanceof HintRequest) {
			Vector<Vector<Edge>> hint = DataUtil.parseHint(response, edgeMap);
			if (hint.size() > 0)
				paramsSet.routeParams.addHint(hint.elementAt(0));
			else {
				Notifier.notify("No hint at current location!");
			}
		}
		else if (request instanceof EvaluationRequest) {
			String res = DataUtil.parseEvaluation(response);
			if ("OK".equals(res)) {
				++prefectStep;
				final int begin = 5;
				if (prefectStep > begin) {
					int size = ResourceUtil.STRING_PREFECT_STEP.length;
					int index = Util.getValue(prefectStep - begin, size - 1, 0);
					Notifier.notify(ResourceUtil.STRING_PREFECT_STEP[index]);
				}
			}
			else if ("INVALID_ROUTE".equals(res)) {
				prefectStep = 0;
				Notifier.notify(ResourceUtil.STRING_INVALID_ROUTE);
			}
			else if ("TIME_OUT".equals(res)) {
				prefectStep = 0;
				Notifier.notify(ResourceUtil.STRING_TIME_OUT);
			}
		}
		return true;
	}

    public void failedRequest(IRequest quest)
    {
    }

    private void updateGameState(Point current, int consumingTime) {
    	if (current == null && consumingTime > 0)
    		return;

    	// take action
    	remainTime -= consumingTime;
    	checkRemainTime(false);
    	updateScreenLocation(current);
    	updateButtonState(current);
    	
    	boolean isArrivalCheckPoint = false;
    	if (!gameover) {
    		isArrivalCheckPoint = checkPassPointArrivaled(current);
    		requestEvaluation();
    		if(isArrivalCheckPoint) {
    			paramsSet.routeParams.clearRoute();
    			paramsSet.routeParams.addPoint(current);
    		}
    	}
    	checkRemainTime(isArrivalCheckPoint);
    	updateTime();
    }
    
	private void requestEvaluation() {
		if (paramsSet.routeParams.getRoutePointSize() >= 2) {
			PassPoint pp = ppTotal.get(currentPpIndex);
			if (null != pp) {
				ThreadPool.getInstance().addJob(
						new EvaluationRequest(this, pp.level, pp.timestamp - (remainTime * 60 * 1000),
								paramsSet.routeParams.getRouteString()));
			}
		}
	}

	private boolean checkPassPointArrivaled(Point current) {
    	Vector<Point> passPoints = new Vector<Point>();
    	paramsSet.edgesParams.copyPassPoints(passPoints);
    	for (Point p : passPoints) {
    		if (p == current) { // ref equal
    		    Notifier.notify("enter the next pass point!");
    			paramsSet.edgesParams.removePassPoints();
    			
    			if (++currentPpIndex < ppTotal.size()) {
    			    PassPoint passPoint = ppTotal.get(currentPpIndex);
    			    Point ppPoint = PointsManager.getPoint((int) passPoint.longitude, (int) passPoint.latitude);
    			    
    			    Vector<Point> pp = new Vector<Point>();
    			    pp.add(ppPoint);
    			    paramsSet.edgesParams.addPassPoints(pp);
    			    
    			    PassPoint lastPoint = ppTotal.get(currentPpIndex - 1);
    			    remainTime += (int) ((passPoint.timestamp - lastPoint.timestamp) / (60 * 1000));
    			}
    			else {
    				if (++gameLevel <= Util.GAME_MAX_LEVEL) {
    					Notifier.notify("finish the level. loading next level...");
    					render.pause();
    					loader.loadGame(new GameDataPackage(gameLevel), this);
    				}
    				else {
    					Notifier.notify("Free content is over, Purchase full version! \nNow only need $.99!");
    				}
    			}
    			return true;
    		}
    	}
    	return false;
    }
    
    private void checkRemainTime(boolean b) {
    	if (remainTime < 0 || (b && remainTime == 0)) {
    		gameover = true;
    		Notifier.notify("you die!");
		}
    }
    
    private void updateScreenLocation(Point current) {
    	int adjustX = 0;
    	int adjustY = 0;
    	int[] screenXY = new int[2];
    	current.getScreenXY(screenXY);
    	
    	if (screenXY[0] < margin[0]) {
    		adjustX = screenXY[0] - margin[0];
    	}
    	else if (screenXY[0] > Util.canvasWidth - margin[0]) {
    		adjustX = screenXY[0] - (Util.canvasWidth - margin[0]);
    	}
    	if (screenXY[1] < margin[1]) {
    		adjustY = screenXY[1] - margin[1];
    	}
    	else if (screenXY[1] > Util.canvasHeight - margin[1]) {
    		adjustY = screenXY[1] - (Util.canvasHeight - margin[1]);
    	}
    	if (adjustX != 0 || adjustY != 0) {
    		paramsSet.edgesParams.move(adjustX, adjustY);
    	}
    }
    
    // @xunliu: This function should have some problem
    // when we come to boundary, should be carefull during updating
    private void updateButtonState(Point current)
    {
        int[] out = new int[2];

        // if col == 0 means we come to the left boundary
        // otherwise we could enable left arrow
        Edge e = null;
		if (current.getColumn() != 0) {
			current.getLeftEdgeIndex(out);
			e = edgeMap.getHorEdge(out[0], out[1]);
		}
        context.findViewById(R.id.left).setEnabled(e != null && e.negDirectionAvailable());

        e = null;
		if (current.getRow() != 0) {
			current.getTopEdgeIndex(out);
			e = edgeMap.getVertEdge(out[0], out[1]);
		}
        context.findViewById(R.id.up).setEnabled(e != null && e.negDirectionAvailable());

		e = null;
		if (current.getColumn() != Util.COLUMN) {
			current.getRightEdgeIndex(out);
			e = edgeMap.getHorEdge(out[0], out[1]);
		}
		context.findViewById(R.id.right).setEnabled(e != null && e.posDirectionAvailable());

		e = null;
		if (current.getRow() != Util.ROW) {
			current.getBottomEdgeIndex(out);
			e = edgeMap.getVertEdge(out[0], out[1]);
		}
        context.findViewById(R.id.down).setEnabled(e != null && e.posDirectionAvailable());
    }

	private void clearSnow(Edge e) {
		if (e != null && e.getSnowNum() > 0) {
			e.setSnowNum(0);
			score += PRE_SNOW_SCORE;
			updateScore();
		}
	}
    
	private Edge getEdge(int[] index, boolean hor) {
		return hor ? edgeMap.getHorEdge(index[0], index[1]) : edgeMap.getVertEdge(index[0], index[1]);
	}

    private TextView scoreView;
    private TextView timeView;

	private void updateScore() {
		if (scoreView != null) {
			scoreView.setText(score + "");
			Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
			vibrator.vibrate(100);
		}
	}
	
	private void updateTime() {
		if (timeView != null) {
			timeView.setText(remainTime <= 0 ? "00:00" : (remainTime < 10 ? "0" : "") + remainTime + ":00");
		}
	}
}
